﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Xml.XPath;

namespace Allegro.TaskExecution
{
    ///<summary>
    ///</summary>
    public class ForEach : Task
    {
        private ITask _doTask;

        ///<summary>
        /// If true, the child tasks will be executed in sequence. If false, 
        /// the tasks will be executed in parallel. Default is true
        ///</summary>
        public bool InSequence { get; set; }
        ///<summary>
        /// Name of iteration variable
        ///</summary>
        public string Var { get; set; }
        ///<summary>
        /// Must be assigned an object of type IEnumerable
        ///</summary>
        public object In { get; set; }
        /// <summary>
        /// Task to execute
        /// </summary>
        public ITask Do
        {
            get
            {
                return _doTask;
            }
            set
            {
                if (value != null)
                {
                    _doTask = value;
                    _doTask.ParentTask = this;
                }
            }
        }

        ///<summary>
        ///</summary>
        public ForEach()
        {
            InSequence = true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="outerContext"></param>
        protected override void ExecuteTask(IExecutionContext outerContext)
        {
            // Check that the task is well defined
            _VerifyTask();

            // Navigate to the task execution context
            IExecutionContext context = GetTaskExecutionContext(this, outerContext);

            ICompositeTaskActivity taskActivity = Activity as ICompositeTaskActivity;
            if (taskActivity != null)
            {
                taskActivity.BeforeExecute(this, context);
            }
            // Check if the execution should be suspended or aborted
            CheckIfSuspendOrAbort(context);

            // Convert In to IEnumerable
            IEnumerable inCollection = null;
            if (In is IEnumerable)
            {
                inCollection = In as IEnumerable;
            }
            else if (In is IContextExpression)
            {
                object value = ((IContextExpression)In).Evaluate(context);
                inCollection = (IEnumerable)value;
            }
            else if(In is EnumerateInContext)
            {
                inCollection = ((EnumerateInContext) In)(context);
            }
            if (inCollection == null)
            {
                string msg = string.Format("ForEach task {0}: Unable to execute because the In member is unassigned",
                                           Name);
                Log.Error(msg, this, LogEventTypes.TaskEvent);
                throw new ApplicationException(msg);
            }
            //else if(In is IEnumerable)
            //{
            //    List<object> list = new List<object>();
            //    foreach(object o in (IEnumerable) In)
            //    {
            //        list.Add(o);
            //    }
            //    inCollection = list;
            //}
            //else if (collection.GetType().IsGenericType && collection.GetType().GetGenericTypeDefinition().Equals(typeof(IEnumerable<>)))
            //{
            //    // This is an IEnumerable<T>
            //}

            if (InSequence)
            {
                ITaskContextDerivation derivation = context as ITaskContextDerivation;
                foreach (object element in inCollection)
                {
                    IExecutionContext childContext = (derivation != null) ? derivation.New(element, Do) : context;
                    childContext[Var] = element;
                    if (taskActivity != null && Do.Activity != null)
                    {
                        taskActivity.PreProcessChildTask(this, Do, context, childContext);
                    }
                    ITaskContextCoordination taskCoordination = childContext as ITaskContextCoordination;
                    if (taskCoordination != null)
                    {
                        taskCoordination.RegisterTaskInContext(Do);
                    }
                    // Execute
                    Do.Execute(childContext);

                    if (taskActivity != null && Do.Activity != null)
                    {
                        taskActivity.PostProcessChildTask(this, Do, context, childContext);
                    }
                    // Check if the execution should be suspended or aborted
                    CheckIfSuspendOrAbort(context);
                }
            }
            else
            {
                ITaskContextDerivation derivation = context as ITaskContextDerivation;
                Dictionary<object, IExecutionContext> childContexts = new Dictionary<object, IExecutionContext>();

                foreach (object element in inCollection)
                {
                    IExecutionContext childContext = (derivation != null) ? derivation.New(element, Do) : context;
                    if (!childContexts.ContainsKey(element))
                        childContexts.Add(element, childContext);
                    else
                    {
                        string msg =
                            string.Format(
                                "Warning: ForEach task found duplicates of object {0} of type {1} in collection. Skipping duplicate entries ",
                                element, element.GetType().FullName);
                        Log.Error(msg, this, LogEventTypes.TaskEvent);
                        Console.WriteLine(msg);
                        continue;
                    }
                    childContext[Var] = element;
                    if (taskActivity != null && Do.Activity != null)
                    {
                        taskActivity.PreProcessChildTask(this, Do, context, childContext);
                    }
                    ITaskContextCoordination taskCoordination = childContext as ITaskContextCoordination;
                    if (taskCoordination != null)
                    {
                        taskCoordination.RegisterTaskInContext(Do);
                    }
                    // Execute
                    ThreadPool.QueueUserWorkItem(RunTask, new TaskExecutionRecord { Context = childContext, Task = Do });
                }

                ITaskContextCoordination coordination = context as ITaskContextCoordination;
                if (coordination != null)
                {
                    ICompositeTaskActivity compositeTaskActivity = Activity as ICompositeTaskActivity;
                    // Wait for all tasks to complete if running in parallel
                    foreach (object element in inCollection)
                    {
                        ITaskContextCoordination taskContextCoordination =
                            childContexts[element] as ITaskContextCoordination;
                        taskContextCoordination.WaitOnTaskCompletion(Do);
                        if (Do.Activity != null && compositeTaskActivity != null)
                        {
                            compositeTaskActivity.PostProcessChildTask(this, Do, context, taskContextCoordination);
                        }
                    }
                }
            }
            if (taskActivity != null)
            {
                taskActivity.AfterExecute(this, context);
            }
        }

        private void _VerifyTask()
        {
            // Var
            if(string.IsNullOrEmpty(Var))
            {
                string msg = string.Format("ForEach task {0} has no iteration variable specified", Oid);
                Log.Error(msg, this, LogEventTypes.TaskEvent);
                throw new ApplicationException(msg);
            }
            // In
            if(In == null)
            {
                string msg = string.Format("ForEach task {0} has no collection to iterate", Oid);
                Log.Error(msg, this, LogEventTypes.TaskEvent);
                throw new ApplicationException(msg);
            }
            // Do
            if(Do == null)
            {
                string msg = string.Format("ForEach task {0} has no task to execute", Oid);
                Log.Error(msg, this, LogEventTypes.TaskEvent);
                throw new ApplicationException(msg);             
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveAttributes(System.Xml.XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            base.SaveAttributes(writer, persistenceContext);

            if (!InSequence)
            {
                // sequential
                writer.WriteAttributeString("sequential", "false");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveContent(System.Xml.XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            base.SaveContent(writer, persistenceContext);

            // Var
            writer.WriteElementString("Var", XmlNamespace, Var);

            // In
            if (In != null)
            {
                writer.WriteStartElement("In", XmlNamespace);
                if (In is IXmlPersist)
                {
                    //IXmlPersist inPersist = In as IXmlPersist;
                    //inPersist.Save(writer, persistenceContext);
                    XmlPersist.SaveObject(writer, In, persistenceContext);
                }
                else if (In is IEnumerable)
                {
                    //writer.WriteAttributeString("useXmlSerializer", "true");
                    XmlPersist.SaveObject(writer, In, persistenceContext);
                    //writer.WriteStartElement("Values", XmlNamespace);
                    //IEnumerable inEnumerable = In as IEnumerable;
                    //foreach (object o in inEnumerable)
                    //{
                    //    XmlPersist.SaveObject(writer, o, persistenceContext);
                    //}
                    //writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
            // Do
            if (Do != null)
            {
                writer.WriteStartElement("Do", XmlNamespace);
                XmlPersist.SaveObject(writer, Do, persistenceContext);
                writer.WriteEndElement();
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadAttributes(System.Xml.XPath.XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            base.LoadAttributes(itemNode, persistenceContext);

            string sequential = itemNode.GetAttribute("sequential", "");
            if (sequential == "false")
                InSequence = false;
            else
            {
                InSequence = true;
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadContent(System.Xml.XPath.XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            base.LoadContent(itemNode, persistenceContext);

            // Var
            XPathNavigator varNode = itemNode.SelectSingleNode("al:Var", persistenceContext.NamespaceManager);
            if (varNode != null)
            {
                Var = varNode.Value;
            }
            // In
            XPathNavigator inNode = itemNode.SelectSingleNode("al:In", persistenceContext.NamespaceManager);
            if (inNode != null)
            {
                XPathNavigator collectionNode = inNode.SelectSingleNode("Collection", persistenceContext.NamespaceManager);
                if (collectionNode != null)
                {
                    // An IEnumerable has been serialized

                }
                else
                {
                    inNode.MoveToFirstChild();
                    In = XmlPersist.LoadObject<object>(inNode, persistenceContext);
                }
            }

            // Do
            XPathNavigator doNode = itemNode.SelectSingleNode("al:Do", persistenceContext.NamespaceManager);
            if (doNode != null)
            {
                doNode.MoveToFirstChild();
                Do = XmlPersist.LoadObject<ITask>(doNode, persistenceContext);
            }
        }

        internal void ProgressUpdate(ITask task, IExecutionContext context, ProgressEventType eventType, double progress, string message)
        {
            PostEvent(task, context, eventType, progress, message);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="context"></param>
        public override void NotifyProgress(TaskExecutionProgressNotification callback, IExecutionContext context)
        {
            base.NotifyProgress(callback, context);

            // Subscribe for events from the child task
            if(Do != null)
                Do.NotifyProgress(ProgressUpdate, context);
        }
    }
}
